/*
    socket.c.tst - Unit tests for mprSocket

    Copyright (c) All Rights Reserved. See details at the end of the file.
 */

/********************************** Includes **********************************/

#include    "testme.h"
#include    "mpr.h"

/*********************************** Locals ***********************************/

typedef struct TestSocket {
    MprSocket       *server;                    /* Server listen socket */
    MprSocket       *accepted;                  /* Server-side accepted client socket */
    MprSocket       *client;                    /* Client socket */
    MprBuf          *inBuf;                     /* Input buffer */
    int             port;                       /* Server port */
} TestSocket;

TestSocket *ts;

static int hasInternet = 0;
static int hasIPv6 = 0;
static int bufsize = 16 * 1024;

/***************************** Forward Declarations ***************************/

static int acceptFn(void *data, MprEvent *event);
static void manageTestSocket(TestSocket *ts, int flags);
static MprSocket *openServer(cchar *host);
static int readEvent(void *data, MprEvent *event);

/************************************ Code ************************************/
/*
    Initialize the TestSocket structure and find a free server port to listen on.
    Also determine if we have an internet connection. 
    This is called per group.
 */
static int initSocket()
{
    MprSocket       *sp;

    if ((ts = mprAllocObj(TestSocket, manageTestSocket)) == 0) {
        return 0;
    }
    mprAddRoot(ts);
    ts->inBuf = mprCreateBuf(0, 0);

    /*
        See if we have an internet connection
     */
    sp = mprCreateSocket(NULL);
    if (mprConnectSocket(sp, "www.google.com", 80, 0) >= 0) {
        hasInternet = 1;
    }
    mprCloseSocket(sp, 0);

    /*
        Check for IPv6 support
     */
    if ((sp = openServer("::1")) != 0) {
        hasIPv6 = 1;
        mprCloseSocket(sp, 0);
    }
    return 0;
}


/*
    Open a server on a free port.
 */
static MprSocket *openServer(cchar *host)
{
    MprSocket       *sp;
    int             port;
    
    if ((sp = mprCreateSocket(NULL)) == 0) {
        return 0;
    }
    for (port = 9175; port < 9250; port++) {
        if (mprListenOnSocket(sp, host, port, MPR_SOCKET_NODELAY | MPR_SOCKET_THREAD) != SOCKET_ERROR) {
            assert(sp->fd != SOCKET_ERROR);
            ts->port = port;
            mprAddSocketHandler(sp, MPR_SOCKET_READABLE, NULL, (MprEventProc) acceptFn, ts, 0);
            return sp;
        }
    }
    return 0;
}


static void manageTestSocket(TestSocket *ts, int flags)
{
    if (flags & MPR_MANAGE_MARK) {
        mprMark(ts->server);
        mprMark(ts->accepted);
        mprMark(ts->client);
        mprMark(ts->inBuf);

    } else if (flags & MPR_MANAGE_FREE) {
        mprCloseSocket(ts->accepted, 0);
        mprCloseSocket(ts->client, 0);
        mprCloseSocket(ts->server, 0);
    }
}


static int acceptFn(void *data, MprEvent *event)
{
    MprSocket       *sp;

    /*
        Windows can return NULL with EAGAIN
     */
    sp = mprAcceptSocket(ts->server);
    if (sp) {
        ttrue(sp->fd >= 0);
        ts->accepted = sp;
        mprAddSocketHandler(sp, MPR_READABLE, NULL, (MprEventProc) readEvent, ts, 0);
        mprSignalCompletion(event->dispatcher);
    }
    return 0;
}


/*
    Read incoming data. Expect to read about 5K worth of data.
 */
static int readEvent(void *data, MprEvent *event)
{
    MprSocket       *sp;
    ssize           nbytes, space;
    char            *buf;
    int             rc;

    sp = ts->accepted;
    space = mprGetBufSpace(ts->inBuf);
    if (space < (bufsize / 2)) {
        rc = mprGrowBuf(ts->inBuf, bufsize - space);
        ttrue(rc == 0);
    }
    buf = mprGetBufEnd(ts->inBuf);
    nbytes = mprReadSocket(sp, buf, mprGetBufSpace(ts->inBuf));

    if (nbytes < 0) {
        mprCloseSocket(sp, 1);
        mprSignalCompletion(event->dispatcher);
        return 1;

    } else if (nbytes == 0) {
        if (mprIsSocketEof(sp)) {
            mprCloseSocket(sp, 1);
            mprSignalCompletion(event->dispatcher);
            return 1;
        }

    } else {
        mprAdjustBufEnd(ts->inBuf, nbytes);
    }
    mprEnableSocketEvents(sp, MPR_READABLE);
    return 0;
}


static void createSocket()
{
    MprSocket       *sp;

    sp = mprCreateSocket(NULL);
    ttrue(sp != 0);
    mprCloseSocket(sp, 0);
}


static void client()
{
    MprSocket       *sp;
    int             depth, rc;

    if (hasInternet) {
        depth = tgeti("TM_DEPTH", 0);
        if (depth > 1 && mprHasSecureSockets()) {
            sp = mprCreateSocket(NULL);
            ttrue(sp != 0);
            rc = mprConnectSocket(sp, "www.google.com", 80, 0);
            ttrue(rc >= 0);
            mprCloseSocket(sp, 0);
        }
    }
}


static void clientServer(cchar *host)
{
    MprTicks        mark;
    ssize           len, thisLen, nbytes;
    char            *buf, *thisBuf;
    int             i, rc, count;

    ts->accepted = 0;
    ts->server = openServer(host);
    ttrue(ts->server != NULL);
    if (ts->server == 0) {
        return;
    }
    ts->client = mprCreateSocket(NULL);
    ttrue(ts->client != 0);
    ttrue(!ts->accepted);

    rc = mprConnectSocket(ts->client, host, ts->port, 0);
    ttrue(rc >= 0);

    mprWaitForCompletion(NULL, TM_SHORT_NAP);
    /*  Set in acceptFn() */
    ttrue(ts->accepted != 0);

    buf = "01234567890123456789012345678901234567890123456789\r\n";
    len = strlen(buf);

    /*
        Write a set of lines to the client. Server should receive. Use blocking mode. This writes about 5K of data.
     */
    mprSetSocketBlockingMode(ts->client, 1);
    count = 100;
    for (i = 0; i < count; i++) {
        /*
            Non-blocking I/O may return a short-write
         */
        thisBuf = buf;
        for (thisLen = len; thisLen > 0; ) {
            nbytes = mprWriteSocket(ts->client, thisBuf, thisLen);
            ttrue(nbytes >= 0);
            thisLen -= nbytes;
            thisBuf += nbytes;
        }
    }
    mprCloseSocket(ts->client, 1);
    ts->client = 0;

    mark = mprGetTicks();
    do {
        if (mprWaitForCompletion(NULL, TM_SHORT_NAP)) {
            break;
        }
    } while (mprGetRemainingTicks(mark, TM_SHORT_NAP) > 0);

    if (mprGetBufLength(ts->inBuf) != (count * len)) {
        mprPrintf("i %d count %d, remaining %zu, buflen %zu, cmp %zu\n", 
            i, count, thisLen, mprGetBufLength(ts->inBuf), count * len);
        mprPrintf("ELAPSED %lld\n", mprGetElapsedTicks(mark));
    }
    ttrue(mprGetBufLength(ts->inBuf) == (count * len));
    mprFlushBuf(ts->inBuf);

    mprCloseSocket(ts->server, 0);
    ts->server = 0;
}


static void clientServerIPv4()
{
    clientServer("127.0.0.1");
}


static void clientServerIPv6()
{
    if (hasIPv6) {
        clientServer("::1");
    }
}


static void clientSslv4()
{
    MprSocket       *sp;
    int             depth, rc;

    if (hasInternet) {
        depth = tgeti("TM_DEPTH", 0);
        if (depth > 1 && mprHasSecureSockets()) {
            sp = mprCreateSocket(NULL);
            ttrue(sp != 0);
            ttrue(sp->provider != 0);
            rc = mprConnectSocket(sp, "www.google.com", 443, 0);
            ttrue(rc >= 0);
            mprCloseSocket(sp, 0);
        }
    }
}


static void cloneSocket()
{
    MprSocket       *sp, *clone;
    int             rc;

    sp = mprCreateSocket(0);
    clone = mprCloneSocket(sp);
    ttrue(sp->fd == clone->fd);
    ttrue(sp->mutex != clone->mutex);

    if (hasInternet) {
        rc = mprConnectSocket(sp, "www.google.com", 80, 0);
        ttrue(rc >= 0);
        clone = mprCloneSocket(sp);
        ttrue(sp->fd != INVALID_SOCKET);
        ttrue(sp->fd == clone->fd);
        ttrue(sp->mutex != clone->mutex);
        mprCloseSocket(sp, 0);
        mprCloseSocket(clone, 0);
    }
}


static void stealSocket()
{
    MprSocket   *sp;
    Socket      fd;
    int         rc;

    sp = mprCreateSocket(0);
    ttrue(sp->fd == INVALID_SOCKET);

    fd = mprStealSocketHandle(sp);
    ttrue(fd == INVALID_SOCKET);

    if (hasInternet) {
        rc = mprConnectSocket(sp, "www.google.com", 80, 0);
        ttrue(rc >= 0);
        fd = mprStealSocketHandle(sp);
        ttrue(fd != INVALID_SOCKET);
        ttrue(sp->fd == INVALID_SOCKET);
        closesocket(fd);
    }
}

int main(int argc, char **argv)
{
    mprCreate(argc, argv, 0);
    mprSetModuleSearchPath(BIN);
    mprVerifySslPeer(NULL, 0);

    initSocket();
    createSocket();
    client();
#if !WIN
    clientServerIPv4();
    clientServerIPv6();
#endif
    clientSslv4();
    cloneSocket();
    stealSocket();
    return 0;
}


/*
    @copy   default

    Copyright (c) Embedthis Software. All Rights Reserved.

    This software is distributed under commercial and open source licenses.
    You may use the Embedthis Open Source license or you may acquire a 
    commercial license from Embedthis Software. You agree to be fully bound
    by the terms of either license. Consult the LICENSE.md distributed with
    this software for full details and other copyrights.

    Local variables:
    tab-width: 4
    c-basic-offset: 4
    End:
    vim: sw=4 ts=4 expandtab

    @end
 */
